describe("ReadHistory module", function()
    local DocSettings
    local DataStorage
    local joinPath
    local mkdir
    local realpath
    local reload
    local lfs
    local now = 61

    local function file(name)
        return joinPath(DataStorage:getDataDir(), name)
    end

    local function test_data_dir()
        return joinPath(DataStorage:getDataDir(), "testdata")
    end

    local function test_file(name)
        return joinPath(test_data_dir(), name)
    end

    local function legacy_history_file(name)
        return DocSettings:getHistoryPath(realpath(test_file(name)))
    end

    local function rm(filename)
        os.remove(filename)
    end

    local function mv(source, target)
        os.rename(source, target)
    end

    local function touch(filename)
        -- Create file if need be
        local f = io.open(filename, "w")
        f:close()
        -- Increment by 61s every time we're called
        now = now + 61
        lfs.touch(filename, now, now)
    end

    local function assert_item_is(h, i, name, fileRemoved)
        assert.is.same(name, h.hist[i].text)
        assert.is.same(i, h.hist[i].index)
        assert.is.same(joinPath(realpath(test_data_dir()), name), h.hist[i].file)
        if fileRemoved then
            assert.is_nil(realpath(test_file(name)))
        else
            assert.is.same(realpath(test_file(name)), h.hist[i].file)
        end
    end

    setup(function()
        require("commonrequire")
        DocSettings = require("docsettings")
        DataStorage = require("datastorage")
        joinPath = require("ffi/util").joinPath
        mkdir = require("libs/libkoreader-lfs").mkdir
        realpath = require("ffi/util").realpath
        reload = function() return package.reload("readhistory") end
        lfs = require("libs/libkoreader-lfs")

        mkdir(joinPath(DataStorage:getDataDir(), "testdata"))
    end)

    it("should read empty history.lua", function()
        rm(file("history.lua"))
        local h = reload()
        assert.is.same(#h.hist, 0)
        touch(file("history.lua"))
        h = reload()
        assert.is.same(#h.hist, 0)
    end)

    it("should read non-empty history.lua", function()
        rm(file("history.lua"))
        local h = reload()
        touch(test_file("a"))
        now = now + 61
        h:addItem(test_file("a"), now)
        h = reload()
        assert.is.same(1, #h.hist)
        assert_item_is(h, 1, "a")
        rm(test_file("a"))
    end)

    it("should order legacy and history.lua", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        touch(legacy_history_file("b"))
        h = reload()
        assert.is.same(2, #h.hist)
        assert_item_is(h, 1, "b")
        assert_item_is(h, 2, "a")
        rm(legacy_history_file("b"))
        rm(test_file("a"))
        rm(test_file("b"))
    end)

    it("should read legacy history folder", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        touch(test_file("d"))
        touch(test_file("e"))
        touch(test_file("f"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("f"), now)
        touch(legacy_history_file("c"))
        touch(legacy_history_file("b"))
        now = now + 61
        h:addItem(test_file("d"), now)
        touch(legacy_history_file("a"))
        now = now + 61
        h:addItem(test_file("e"), now)
        h = reload()
        assert.is.same(6, #h.hist)
        assert_item_is(h, 1, "e")
        assert_item_is(h, 2, "a")
        assert_item_is(h, 3, "d")
        assert_item_is(h, 4, "b")
        assert_item_is(h, 5, "c")
        assert_item_is(h, 6, "f")

        rm(legacy_history_file("c"))
        rm(legacy_history_file("b"))
        rm(legacy_history_file("a"))
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
        rm(test_file("d"))
        rm(test_file("e"))
        rm(test_file("f"))
    end)

    it("should add item", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        assert.is.same(1, #h.hist)
        assert_item_is(h, 1, "a")
        rm(test_file("a"))
    end)

    it("should be able to remove the first item", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        local h = reload()
        -- NOTE: Identical timestamps to neuter sorting by mtime, instead alphabetical order kicks in (c.f., ReadHistory:_sort)
        --       This goes for basically the rest of the tests.
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        h:addItem(test_file("c"), now)
        h:removeItem(h.hist[1])
        assert_item_is(h, 1, "b")
        assert_item_is(h, 2, "c")
        h:removeItem(h.hist[1])
        assert_item_is(h, 1, "c")
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
    end)

    it("should be able to remove an item in the middle", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        h:addItem(test_file("c"), now)
        h:removeItem(h.hist[2])
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "c")
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
    end)

    it("should be able to remove the last item", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        h:addItem(test_file("c"), now)
        h:removeItem(h.hist[3])
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "b")
        h:removeItem(h.hist[2])
        assert_item_is(h, 1, "a")
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
    end)

    it("should be able to remove two items", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        touch(test_file("d"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        h:addItem(test_file("c"), now)
        h:addItem(test_file("d"), now)
        h:removeItem(h.hist[3])  -- remove c
        h:removeItem(h.hist[2])  -- remove b
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "d")
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
        rm(test_file("d"))
    end)

    it("should be able to remove three items", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        touch(test_file("c"))
        touch(test_file("d"))
        touch(test_file("e"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        h:addItem(test_file("c"), now)
        h:addItem(test_file("d"), now)
        h:addItem(test_file("e"), now)
        h:removeItem(h.hist[2])  -- remove b
        h:removeItem(h.hist[2])  -- remove c
        h:removeItem(h.hist[3])  -- remove e
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "d")
        rm(test_file("a"))
        rm(test_file("b"))
        rm(test_file("c"))
        rm(test_file("d"))
        rm(test_file("e"))
    end)

    it("should remove duplicate entry", function()
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("b"), now)
        now = now + 61
        h:addItem(test_file("b"), now)
        touch(legacy_history_file("a"))
        now = now + 61
        h:addItem(test_file("a"), now)  -- ensure a is before b
        h = reload()
        assert.is.same(2, #h.hist)
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "b")

        rm(legacy_history_file("a"))
        rm(test_file("a"))
        rm(test_file("b"))
    end)

    it("should reduce the total count", function()
        local function to_file(i)
            return test_file(string.format("%04d", i))
        end
        rm(file("history.lua"))
        local h = reload()
        for i = 1000, 1, -1 do
            touch(to_file(i))
            h:addItem(to_file(i))
        end

        for i = 1, 500 do  -- at most 500 items are stored
            assert_item_is(h, i, string.format("%04d", i))
        end

        for i = 1, 1000 do
            rm(to_file(i))
        end
    end)

    it("should reload the history file if it updated", function()
        -- Prepare a history.lua file with two items a and b.
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)
        mv(file("history.lua"), file("history.backup"))

        h = reload()
        assert.is.same(0, #h.hist)
        mv(file("history.backup"), file("history.lua"))
        h:reload()

        assert.is.same(2, #h.hist)
        assert_item_is(h, 1, "a")
        assert_item_is(h, 2, "b")

        rm(test_file("a"))
        rm(test_file("b"))
    end)

    local function testAutoRemoveDeletedItems()
        -- Prepare a history.lua file with two items a and b.
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)

        rm(test_file("a"))

        h:reload()
        assert.is.same(1, #h.hist)
        assert_item_is(h, 1, "b")

        rm(test_file("b"))
    end

    local function testDoNotAutoRemoveDeletedItems()
        -- Prepare a history.lua file with two items a and b.
        rm(file("history.lua"))
        touch(test_file("a"))
        touch(test_file("b"))
        local h = reload()
        now = now + 61
        h:addItem(test_file("a"), now)
        h:addItem(test_file("b"), now)

        rm(test_file("a"))

        h:reload()
        assert.is.same(2, #h.hist)
        assert_item_is(h, 1, "a", true)
        assert_item_is(h, 2, "b")

        rm(test_file("b"))
    end

    it("should automatically remove deleted items from history if setting has been set",
       function()
           G_reader_settings:saveSetting("autoremove_deleted_items_from_history", true)
           testAutoRemoveDeletedItems()
           G_reader_settings:delSetting("autoremove_deleted_items_from_history")
       end)

    it("should not automatically remove deleted items from history if setting has not been set",
       function()
           G_reader_settings:delSetting("autoremove_deleted_items_from_history")
           testDoNotAutoRemoveDeletedItems()
       end)

    it("should not automatically remove deleted items from history if setting has been set to false",
       function()
           G_reader_settings:saveSetting("autoremove_deleted_items_from_history", false)
           testDoNotAutoRemoveDeletedItems()
           G_reader_settings:delSetting("autoremove_deleted_items_from_history")
       end)
end)
